package com.globo.opensocial;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Writer;
import java.util.Stack;
import java.util.Vector;

import org.htmlparser.Node;
import org.htmlparser.PrototypicalNodeFactory;
import org.htmlparser.filters.OrFilter;
import org.htmlparser.filters.TagNameFilter;
import org.htmlparser.lexer.PageAttribute;
import org.htmlparser.nodes.RemarkNode;
import org.htmlparser.nodes.TagNode;
import org.htmlparser.nodes.TextNode;
import org.htmlparser.tags.BodyTag;
import org.htmlparser.tags.Html;
import org.htmlparser.tags.ImageTag;
import org.htmlparser.tags.LinkTag;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.SimpleNodeIterator;

import com.globo.opensocial.tags.ForTag;
import com.globo.opensocial.tags.IfTag;
import com.globo.opensocial.tags.IncludeTag;

public class Template extends Content {

	// private String instanceName;

	private int instanceCount = 0;

	Parser parser = null;

	private Stack<String> nodes;

	private ImageProxy imageProxy;

	/**
	 * Template
	 * 
	 * @param fileName
	 */
	public Template(Application app, File file) {
		super(app, file);
		System.out.println("[INFO] Reading template " + file.getName());
		parser = new Parser(app);
		nodes = new Stack<String>();
		imageProxy = new ImageProxy(app.getHost(), parser);
	}

	/**
	 * Returns the ROOT (body) Node from Template.
	 * 
	 * @throws FileNotFoundException
	 */
	Node parseDom() throws FileNotFoundException {

		try {
			org.htmlparser.Parser htmlParser;
			htmlParser = new org.htmlparser.Parser(file.getAbsolutePath());

			TagNameFilter bodyFilter = new TagNameFilter("body");

			OrFilter orFilter = new OrFilter(new TagNameFilter("html"),
					bodyFilter);

			PrototypicalNodeFactory factory = (PrototypicalNodeFactory) htmlParser
					.getNodeFactory();

			// Register my custom tags.
			factory.registerTag(new IfTag());
			factory.registerTag(new ForTag());
			factory.registerTag(new IncludeTag());

			NodeList list = htmlParser.parse(orFilter);

			if (list.size() == 0) {
				error(0, "Need html > body structure.");
			} else {
				Node root = list.elementAt(0);
				if (root instanceof Html) {

					NodeList nodes = root.getChildren()
							.extractAllNodesThatMatch(bodyFilter);
					if (nodes.size() != 1) {
						error(0, "Need html > body structure.");
					} else {
						root = nodes.elementAt(0);
					}
				}
				return root;
			}

		} catch (Exception e) {
			// TODO: handle exception
		}
		return null;
	}

	/**
	 * Generate the code generation.
	 * 
	 * @return
	 * @throws FileNotFoundException
	 */
	public void writeCode(Writer out) throws FileNotFoundException {

		StringBuilder buffer = new StringBuilder();

		// Build the "get[template]Page" method
		String getMethod = Util.getMethodName(file.getName(), "get");

		info("generating " + getMethod);

		// buffer.append("function " + getMethod + "( data ) {\n");
		// buffer.append("var __td = data || new osg.TemplateData();\n");

		// buffer.append("};\n");

		buffer.append("/* template:" + file.getName() + " */");
		buffer.append("\nosg.Template.register('");
		buffer.append(Util.getKey(file.getName()));
		buffer.append("', function() {\n");
		appendTemplate(buffer);
		buffer.append("});\n\n");

		// Build the "set[template]Page" function
		// String setMethod = Util.getMethodName(file.getName(), "set");

		/*
		 * info("Generating " + setMethod);
		 * 
		 * buffer.append("\nfunction " + setMethod + "( obj, divId ) {\n");
		 * buffer.append("osg.setPage(" + getMethod + "(obj), divId);\n");
		 * buffer.append("};\n");
		 */

		try {
			JavaScript.writeJs(buffer.toString(), out);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void appendTemplate(StringBuilder out) throws FileNotFoundException {
		parseNode((TagNode) parseDom(), null, out);
	}

	/**
	 * Parse the node and turns it on a JavaScript code.
	 * 
	 * @param node
	 * @param parent
	 * @return
	 */
	public void parseNode(TagNode node, String parent, StringBuilder out) {

		debug("parsing " + node.getRawTagName());

		// handle <c:if> tag.
		if (node instanceof IfTag) {

			IfTag iftag = (IfTag) node;

			if (!iftag.isValid()) {
				error(node.getStartingLineNumber(),
						"<c:if> without test, ignoring.");
			}

			iftag.appendBegin(out, parser);
			processChildNodes(nodes.lastElement(), iftag, out);
			iftag.appendEnd(out);

			// handle <c:forEach>
		} else if (node instanceof ForTag) {

			ForTag forTag = (ForTag) node;

			if (!forTag.isValid()) {
				error(node.getStartingLineNumber(), "forEach without itens.");
			}

			forTag.appendBegin(out, parser);
			parser.push(forTag);

			processChildNodes(nodes.lastElement(), forTag, out);

			parser.pop();
			forTag.appendEnd(out);

			// <c:include>
		} else if (node instanceof IncludeTag) {
			IncludeTag includeTag = (IncludeTag) node;

			if (includeTag.isValid()) {
				out.append(parent);
				out.append(".appendChild(");
				Util.appendInclude(includeTag.getTemplate(), out);
				out.append(");\n");
			} else {
				error(node.getStartingLineNumber(),
						"include without 'template' parameter.");
			}

			// <img> tag.
		} else if (node instanceof ImageTag) {

			ImageTag imageTag = (ImageTag) node;

			String src = node.getAttribute("src");
			if (src == null) {
				error(node.getStartingLineNumber(),
						"<img> tag without src parameter");
			} else {
				nodes.add(getNewInstance());
				out.append("var ");
				out.append(nodes.lastElement());
				imageProxy.append(imageTag, out);
				appendParameters(nodes.lastElement(), node, out);
			}

		} else {

			String nodeName = node.getRawTagName();

			if (node instanceof BodyTag) {
				nodeName = "div";
			}

			nodes.add(getNewInstance());

			Util.creteElementById(nodeName, nodes.lastElement(), out);

			appendParameters(nodes.lastElement(), node, out);

			processChildNodes(nodes.lastElement(), node, out);

		}

		if (Util.isSpecialTag(node)) {
			return;
		}

		// last child.
		if (parent == null) {
			out.append("return _0;\n");

			// append this child.
		} else if (nodes.size() > 0) {
			out.append(parent);
			out.append(".appendChild(");
			out.append(nodes.lastElement());
			out.append(");\n");
			// out.append("//#" + node.getTagName() + "\n");
			nodes.pop();
		}

	}

	private void error(int line, String string) {
		System.out.println("[ERROR," + line + "] " + string);
	}

	private void processChildNodes(String self, TagNode node, StringBuilder out) {

		// without child-nodes, <br />
		NodeList nodes = node.getChildren();
		if (nodes == null || nodes.size() == 0) {
			return;
		}

		SimpleNodeIterator it = nodes.elements();
		while (it.hasMoreNodes()) {

			Node n = it.nextNode();

			if (n instanceof TextNode) {

				String text = n.getText().trim();

				if (text.length() == 0) {
					continue;
				}

				out.append(self + ".appendChild( osg.T(\"");
				out.append(parser.filterText(text));
				out.append("\"));\n");

			} else if (n instanceof RemarkNode) {
				// nothing todo :), just a comment.
			} else {
				parseNode((TagNode) n, self, out);
			}
		}
	}

	private void debug(String string) {
		System.out.println("[DEBUG] " + string);
	}

	private void appendParameters(String self, TagNode node, StringBuilder out) {

		Vector<PageAttribute> v = node.getAttributesEx();

		if (v.size() == 0) {
			return;
		}

		for (PageAttribute attribute : v) {
			if (attribute.isValued() && attribute.getName() != null) {

				String name = attribute.getName().toLowerCase();

				// I don't handle style tag.
				if (name.equals("style")) {
					continue;
				}

				if (name.equals("class")) {
					name = "className";
				}

				String value = attribute.getValue();

				if (name.equals("href") && node instanceof LinkTag
						&& value.startsWith("app.")) {
					out.append(self + ".onclick = ");
					out.append(parser.getSafeFunction(value));
					out.append("\n");
					out.append(self + ".href = \"javascript:void(0);\"\n");
					continue;
				}

				if (node instanceof ImageTag && name.equals("src")) {
					continue;
				}

				out.append(self);
				out.append(".");
				out.append(name);
				out.append(" = ");

				// TODO: "on" might be to
				// fix with possible DOM events

				if (name.startsWith("on")) {
					out.append(parser.getSafeFunction(value));
				} else {
					out.append("\"");
					out.append(parser.filterText(value));
					out.append("\";");
				}
				out.append("\n");
			}
		}
	}

	/**
	 * Create the next JS Instance name.
	 * 
	 * @return instance name
	 */
	private String getNewInstance() {
		String unsignedHex = Integer.toHexString(instanceCount++);
		return "_" + unsignedHex;
	}

	private void info(String string) {
		System.out.println("[INFO] " + string);
	}

}
